package cluster

import (
	"JoRedis/config"
	"JoRedis/database"
	redis2 "JoRedis/interface/redis"
	"JoRedis/interface/reply"
	"JoRedis/lib/logger"
	"JoRedis/protocol"
	"JoRedis/util"
	"net"
	"strconv"
)

type Cluster struct {
	// 节点本身
	self string
	// 集群节点连接Map: key:节点地址, value:集群节点客户端
	nodeAddrKeyClientMap map[string]*RedisClient
	// 一致性哈希函数
	consistentHashPtr *ConsistentHash
	// 底层的数据库
	databasePtr *database.DataBase
}

func MakeCluster() *Cluster {
	nodeAddrSlc := config.PropPtr.Peers
	clusterPtr := &Cluster{
		self:                 config.PropPtr.Self,
		databasePtr:          database.MakeDataBase(),
		nodeAddrKeyClientMap: make(map[string]*RedisClient),
		consistentHashPtr:    MakeConsistentHash(),
	}

	// 遍历集群节点,创建连接
	for _, nodeAddr := range nodeAddrSlc {
		// 哈希一致性添加节点
		clusterPtr.consistentHashPtr.AddNode(nodeAddr)
		// 节点本身跳过
		if nodeAddr == config.PropPtr.Self {
			continue
		}
		clusterPtr.createClusterConn(nodeAddr)
	}
	return clusterPtr
}

// createClusterConn 创建集群连接
func (this *Cluster) createClusterConn(nodeAddr string) {
	nodeConn, err := net.Dial("tcp", nodeAddr)
	if err != nil {
		logger.Info(nodeAddr + " 未连接! ")
	} else {
		clusterClientPtr := MakeClusterClient(nodeConn)
		// 开启集群节点数据读取
		go clusterClientPtr.Read()
		this.nodeAddrKeyClientMap[nodeAddr] = clusterClientPtr
	}
}

// Exec 集群指令执行
func (this *Cluster) Exec(redisConn redis2.RedisConnItf, cmdBytesSlc [][]byte) reply.ReplyItf {

	cmdName := string(cmdBytesSlc[0])
	if cmdName == "select" {
		return database.ExecuteSelect(redisConn, cmdBytesSlc)
	}

	dataKey := cmdBytesSlc[1]
	pickNodeAddr := this.consistentHashPtr.PickNode(string(dataKey))
	if pickNodeAddr == config.PropPtr.Self {
		// 本机执行
		return this.databasePtr.Exec(redisConn, cmdBytesSlc)
	} else {
		// 集群节点执行
		nodeClientPtr, ok := this.nodeAddrKeyClientMap[pickNodeAddr]
		if !ok {
			// 不存在连接--创建
			this.createClusterConn(pickNodeAddr)
			nodeClientPtr = this.nodeAddrKeyClientMap[pickNodeAddr]
		}
		selectDBCmdBytesSlc := util.StrToBytesSlc("select", strconv.Itoa(redisConn.GetDBIdx()))
		_ = nodeClientPtr.Write(protocol.MakeMultiBulkReply(selectDBCmdBytesSlc).ToBytes())
		respPayloadPtr := nodeClientPtr.Write(protocol.MakeMultiBulkReply(cmdBytesSlc).ToBytes())
		return respPayloadPtr.DataReply
	}
}

// Close 关闭集群连接
func (this *Cluster) Close() {
	for _, nodeClient := range this.nodeAddrKeyClientMap {
		nodeClient.Close()
	}
}
